/*
 * Copyright (c) 2000
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Written by Tom Henderson
 * Version 1.2: August 2000 
 */

#ifdef STP
#include <sys/param.h>
#include <sys/systm.h> 
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/errno.h>
 
#include <net/if.h>
#include <net/route.h>
   
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_pcb.h>
#include <netinet/ip_var.h>
#include <netinet/stp.h>
#include <netinet/stp_var.h>

struct inpcbhead scb;
struct inpcbinfo scbinfo;

void    
stp_input(m, iphlen)
	register struct mbuf *m;
	int iphlen;
{
	struct stpcb *sp = 0;
	struct stpiphdr *si;
	struct inpcb *inp;
	struct socket *so;
	u_long seqno;
	u_char pkttype;
	u_long timestamp;
	u_long *dataptr;
	u_short pdu_instance;
	u_long timestamp_recv = 0;
	u_long window_recv = 0;
	u_char response_type = 0;
	int is_urgent = 0;

	/* 
	 * Get IP and STP header together in first mbuf (IP leaves IP hdr
	 * in first mbuf)
	 */
	si = mtod(m, struct stpiphdr *);

	/* This is a FreeBSD only fix */
	/* si_* points to elements in ipovly structure */
	si->si_len = ((struct ip *)si)->ip_len;

	/* strip any IP options */
	if (iphlen > sizeof (struct ip))
		ip_stripoptions(m, (struct mbuf *) 0);
	/* if the number of bytes in the 1st mbuf < 36, call mpullup */
	/* XXX note for some reason, m_len is unreliable on FreeBSD */
	if (m->m_len < sizeof(struct stpiphdr)) {
		if (( m = m_pullup(m, sizeof(struct stpiphdr))) == 0) {
			return;
		}
		si = mtod(m, struct stpiphdr *);
	}

	/* Checksum check */
	if (si->si_sum = stp_cksum(m, ((struct ip *) si)->ip_len)) { 
		DBG(printf("Checksum failure\n"););
		goto drop;
	}

	seqno = ntohl(si->si_seq);
	/* Extract type field */ 
	pkttype = (u_char) (seqno >> 24);	
	/* Zero out highest 8 bits of sequence number field */
	seqno = ((seqno << 8) >> 8);
	pdu_instance = ntohs(si->si_inc);
	/* 
	 * Now that we have the 24 bit sequence number and the pkttype is 
	 * stored separately, overwrite the field in the mbuf with the 
	 * (host order) value, for later use.
	 */
	si->si_seq = seqno;

	/* Get pcb for segment. We don't implement the one-behind cache */
	inp = in_pcblookuphash(&scbinfo, si->si_src, si->si_sport, si->si_dst, 
	    si->si_dport, 1);

	/* 
	 * If no pcb, we're in CLOSED state.  Generate a RST for the 
	 * received packet, unless it itself is a RST (in which case drop).
	 */
	if (inp == 0) {
		if (pkttype != STP_PDU_RST) {
			stp_reset(m, si); 
			return;
		} else {
			m_freem(m);
			return;
		}
	}
	sp = intostpcb(inp);

	if (sp == 0) {
		stp_reset(m, si); 
		return;
	}
	/* Drop if broadcast or multicast */
	if (m->m_flags & (M_BCAST | M_MCAST) ||
		IN_MULTICAST(si->si_dst.s_addr)) {
		m_freem(m);
		return;
	}
	if (sp->s_state == STP_CLOSED) {
		/* Can this happen?? If so, ignore */
		m_freem(m);
		return;
	}
	/* Check for valid PDU types */
	if (!validate_packet(pkttype))
		goto drop;
	
	/* process RESET immediately (otherwise we will mistake it for */
	/* other packet types).					       */
	if (IS_RST_PDU(pkttype)) {
                stp_rcvrst(so, sp, m);
                goto drop;
        }

	if (IS_URG_PDU(pkttype)) {
		/* get rid of these flags so we don't confuse them */
		/* with their other meanings.                      */
		pkttype &= ~STP_PDU_URG;
		is_urgent = 1;
        }

	/* Create new socket if BGN PDU arrives for listening socket */
	so = inp->inp_socket;
	if ((so->so_options & SO_ACCEPTCONN) && (sp->s_state == STP_LISTEN) &&
	    IS_BGN_PDU(pkttype)) {
		so = sonewconn(so,0);
		if (so == 0)
			goto drop;

		inp = (struct inpcb *) so->so_pcb;
		inp->inp_laddr = si->si_dst;
		inp->inp_lport = si->si_dport;
		in_pcbrehash(inp);
			
		/* if input datagram had source route, this fetches it */
		inp->inp_options = ip_srcroute();
	
		sp = intostpcb(inp);
		/* state of new socket set to LISTEN, and s_instance is set */
		sp->s_state = STP_LISTEN;
		sp->s_instance = ntohs(si->si_inc);
		/* start any connection maintenance timers here */
	} else {
		/* 
		 * Make sure packet belongs to this instance of connection.
		 * Don't respond to a RST packet for a nonexistent connection 
		 */
		if ((sp->s_instance != pdu_instance)&& pkttype != STP_PDU_RST) {
			if (IS_BGN_PDU(pkttype)) {
				/* Drop current connection instance */
				stp_close(sp);
				m_freem(m);
				return;
			}  else  {
				stp_reset(m, si); 
				return;
			}
		}
	}

#ifdef STP_RANDOM_DROP
	if ( (u_short) random() > STP_THRESHOLD ) {
		m_freem(m);
		return;
	}
#endif
	sp->s_idle = 0;
	
	/* Pull out window and timestamp fields */
	dataptr = (u_long *) (mtod(m, caddr_t) + 
	    sizeof(struct stpiphdr));
	if (IS_BGN_PDU(pkttype) || IS_BGNAK_PDU(pkttype) ||
	    IS_STAT_PDU(pkttype)) {
		window_recv = ntohl(*dataptr);
		dataptr++;
		timestamp_recv = ntohl(*dataptr);
		dataptr++;
	}
	if (IS_POLL_PDU(pkttype)) {
		timestamp_recv = ntohl(*dataptr);
		dataptr++;
	}
	/*
	 * Handle packet in this order; if concatenated PDU types, they
	 * are handled temporally as if they came in separately
         * i) BGN-related
	 * ii) data-related
	 * iii) END-related
	 *
	 * If packet requires a response, mark the appropriate bits in the
	 * response_type flag
	 *
	 * Note1:  if this is a SD_POLL or SD_END, the seqno used in the
	 *         call to POLL or END must be 1 greater than that used for SD
	 *
	 * Note2:  if this is a BGNAK_STAT, the seqno will correspond to the
	 *         peer's snd_iss.  Therefore, for the STAT, use the value
	 *         (snd_iss + 1) as the seqno in the function call
	 */

	if (IS_BGN_PDU(pkttype)) {
		response_type |=
		    stp_rcvbgn(so, sp, inp, m, seqno, window_recv, 
		    timestamp_recv);
        }
	if (IS_BGNAK_PDU(pkttype)) {
		stp_rcvbgnak(so, sp, seqno, window_recv, timestamp_recv);
	}
	if (IS_SD_PDU(pkttype)) {
		/* First, we must strip off any header fields */
		m->m_data += sizeof(struct stpiphdr); /* Common header */
		m->m_len -= sizeof(struct stpiphdr);
		if (IS_BGN_PDU(pkttype) || IS_BGNAK_PDU(pkttype) ||
		    IS_STAT_PDU(pkttype)) 
			m_adj(m, 8); /* Trim off window and timestamp */
		if (IS_POLL_PDU(pkttype)) 
			m_adj(m, 4); /* Trim off timestamp */
		/* Next, try to process in ``fast path'' */
		if (((sp->s_state == STP_EST) ||
		    (sp->s_state == STP_HALF_CLOSE_PENDING) ||
		    (sp->s_state == STP_HALF_CLOSED) ||
		    (sp->s_state == STP_FULL_CLOSE_PENDING) ||
		    (sp->s_state == STP_FULL_CLOSED))) {
			if (is_urgent) {
				stp_rcvurg(si, so, sp, m, seqno);
			} else {
				if ( (seqno == sp->rcv_max) &&
				    (sp->rcv_max == sp->rcv_nxt) &&
				    (si->si_len <= sbspace(&so->so_rcv)) &&
				    (sp->rcv_wnd)   ) {
					/* Main (fast path) processing */
			 		sbappend(&so->so_rcv, m);
					sorwakeup(so);
					sp->rcv_max = SEQ_INC(sp->rcv_max);
					sp->rcv_nxt = SEQ_INC(sp->rcv_nxt);
				} else {
					response_type |= 
						stp_rcvsd(si, so, sp, m, seqno);
				}
                        }
                }
	}
	if (IS_SD_PDU(pkttype))
		seq_add(seqno, 1); /* Note1 above */

	if (IS_POLL_PDU(pkttype)) {
		response_type |= stp_rcvpoll(sp, seqno, timestamp_recv);
		if (sp->s_state == STP_CLOSED) {
			DBG(printf("Drop due to error: pkttype %d\n", pkttype););
			goto drop; /* if dropped due to protocol error */
		}
		if (is_urgent && !IS_SD_PDU(pkttype)) {
			/* 
			 * This is a bit of a hack.  The POLL indicates that 
			 * urgent data is on its way, but we need to indicate 
			 * the byte offset in the socket's receive buffer 
			 * (so_oobmark).  But we don't know how far in advance 
			 * the urgent data will be, so set so_oobmark to value 
			 * higher than the amount of data in the receive 
			 * buffer, to prevent it from reading right now.
			 * When the actual SDP_URG_PDU comes, we can adjust it 
			 * to the correct value.
			 * Also, we probably should signal only once.
			 */ 
			so->so_oobmark = so->so_rcv.sb_cc + (sp->s_peermaxseg * 
			    seq_subtract(sp->rcv_max, sp->rcv_nxt)) + 1;
			sohasoutofband(so);
			sp->s_oobflags &= ~(STPOOB_HAVEDATA | STPOOB_HADDATA);
			sp->oob_pending = TRUE;
		}
	}
	if (IS_STAT_PDU(pkttype)) {
		stp_rcvstat(so, sp, m, /* see Note2 above */
					IS_BGNAK_PDU(pkttype) ?
					    seq_add(sp->snd_iss, 1) :
					    seqno,
					window_recv, timestamp_recv);
		if (sp->s_state == STP_CLOSED) {
			DBG(printf("Drop due to error: pkttype %d\n", pkttype););
			goto drop; /* if dropped due to protocol error */
		}
	}
	if (IS_USTAT_PDU(pkttype)) {
		stp_rcvustat(so, sp, dataptr, seqno);

		if (sp->s_state == STP_CLOSED) {
			DBG(printf("Drop due to error:  pkttype %d\n", pkttype););
			goto drop; /* if dropped due to protocol error */
		}
	}
	if (IS_END_PDU(pkttype))
		response_type |= stp_rcvend(so, sp, seqno);
	/* If we've freed the socket based on receiving an END, rcvend() will */
	/* return a RST_PDU value.  Force an exit from this function */
	if (IS_RST_PDU(response_type)) {
		m_freem(m);
		return;
	}

	if (IS_ENDAK_PDU(pkttype))
		stp_rcvendak(so, sp, seqno);
	
	/*
	 * Send appropriate PDU based on bits accumulated in response_type
	 */
	if (IS_BGNAK_PDU(response_type)) {
		if (IS_ENDAK_PDU(response_type))
			response_type &= ~(STP_PDU_STAT);
		stp_sndcc_open(sp, response_type);
	} else if (IS_STAT_PDU(response_type) && !IS_ENDAK_PDU(response_type)) {
		stp_sndstat(sp, timestamp_recv);
	} else if (IS_USTAT_PDU(response_type) && !IS_STAT_PDU(response_type)) {
		stp_sndustat(sp);
        }

	if (!IS_SD_PDU(pkttype)) 
		m_freem(m);

	return;
drop:
	m_freem(m);
	DBG(printf("Dropped\n"););
	return;
}

int validate_packet(pkttype)
	u_char pkttype;
{
	if (pkttype == STP_PDU_SD || pkttype == STP_PDU_POLL ||
	    pkttype == STP_PDU_SD_POLL || pkttype == STP_PDU_STAT)
	return 1;
	if (pkttype == STP_PDU_USTAT || pkttype == STP_PDU_BGN ||
	    pkttype == STP_PDU_BGNAK || pkttype == STP_PDU_END ||
	    pkttype == STP_PDU_ENDAK || pkttype == STP_PDU_RST)
		return 1;
	if (pkttype == STP_PDU_BGN_SD_POLL || pkttype == STP_PDU_BGNAK_STAT ||
	    pkttype == STP_PDU_BGN_SD_POLL_END ||
	    pkttype == STP_PDU_BGNAK_ENDAK || pkttype == STP_PDU_POLL_END ||
	    pkttype == STP_PDU_SD_POLL_END);
		return 1;
	DBG(printf("Error-- invalid packet type received %x\n", pkttype););
	return 0;
}

/*
 * Pull out of band byte out of a segment so
 * it doesn't appear in the user's data queue.
 * It is still reflected in the segment length for
 * sequencing purposes.
 * Note:  Unlike TCP, the OOB byte is the first and only data byte of the mbuf.
 */
void
stp_pulloutofband(so, si, m)
	struct socket *so;
	struct stpiphdr *si;
	register struct mbuf *m;
{
	char *cp = mtod(m, caddr_t);
	struct stpcb *sp = sotostpcb(so);
	
	sp->s_iobc = *cp;
	sp->s_oobflags |= STPOOB_HAVEDATA;
	m->m_len--;

}

int 
acked_byte_count(sp, seqno)
	register struct stpcb *sp;	
	u_long seqno;
{
	int wrap = 0;
	int seqno_mod, snd_una_mod;
	int count = 0;
	int i;

	/* 
	 * seqno is the next in sequence expected (by the receiver), so 
	 * (seqno - 1) is actually the last one acked.  
	 * snd_una is the first one acked. 
	 */
	seqno_mod = seqno % MAXBUFFER_STP;
	snd_una_mod = sp->snd_una % MAXBUFFER_STP;

	if (seqno_mod < snd_una_mod) 
		wrap = 1; 	/* wrap around modulo buffer */

	if (!wrap) {
		for (i = snd_una_mod; i < seqno_mod; i++)	
			count += sp->trans_buffer[i].num_bytes;
	} else {
		for (i = snd_una_mod; i < MAXBUFFER_STP; i++)
			count += sp->trans_buffer[i].num_bytes;
		for (i = 0; i < seqno_mod; i++)
			count += sp->trans_buffer[i].num_bytes;
	}
	return count;
}

/*
 * This function places a received packet in the reassembly chain, and passes
 * any (newly) resequenced, contiguous data to the user process.  It returns
 * a value of 1 if an insertion was made, and a value of 0 if no insertion 
 * was made.
 */
int  
stp_reass(sp, si, m)
	struct stpcb *sp;
	struct stpiphdr *si;
	struct mbuf *m;
{
	u_long seqno = si->si_seq;
	int seqno_mod = si->si_seq % MAXBUFFER_STP;
	struct stpiphdr *q;
	struct socket *so = sp->s_inpcb->inp_socket;
	
	/*
	 * Call with si==0 after becoming established to 
	 * force pre-ESTABLISHED data up to user socket.
	 */
	if (si==0)
		goto present;

	/* 
	 * Find a packet with a sequence number >= this one
	 */
	for (q = sp->seg_next; q != (struct stpiphdr *) sp; 
	    q = (struct stpiphdr *) q->si_next) {
		if (seq_gt(q->si_seq, seqno))
			break;
	}
	if (q->si_seq == seqno) { /* received duplicate */
		/* 
	 	 * Unlike TCP, fast path processing may fail (rcv_max > rcv_nxt)
		 * even if there is nothing in the reassembly buffer.  
		 * e.g. if first packet gets lost, and POLL advances rcv_max
		 */
		if (sp->seg_next == sp->seg_prev) {
			if ((seqno == sp->rcv_nxt) && 
			    (si->si_len <= sbspace(&so->so_rcv))
			    && sp->rcv_wnd) {
				sbappend(&so->so_rcv, m);
				sorwakeup(so);
				sp->rcv_nxt = SEQ_INC(sp->rcv_nxt);
				if (seq_geq(seqno, sp->rcv_max))
					sp->rcv_max = SEQ_INC(sp->rcv_max);
				return (1);
			}
		} else {
			DBG(printf("Received duplicate retransmission\n"););
			m_freem(m);
			return (0);
		}
	}
	
	/* Want to avoid doing m_pullup on incoming packets, so use this macro*/
	REASS_MBUF_STP(si) = m;
	insque(si, q->si_prev);  /* Stick new packet in its place.*/
	sp->recv_buffer[seqno_mod].seqno = seqno;
	sp->recv_buffer[seqno_mod].received = 1;
	sp->recv_buffer[seqno_mod].num_bytes = m->m_len;

present:
	/*
	 * Present data to user, advancing rcv_nxt through
	 * completed sequence space.
	 */
	if (sp->s_state < STP_EST)
		return (1);
	si = sp->seg_next;
	/* if the seqno of the first segment on list != rcv_nxt, return */
	if (si == (struct stpiphdr *) sp || si->si_seq != sp->rcv_nxt)
		return (1);
	do {
		sp->rcv_nxt = SEQ_INC(sp->rcv_nxt);
		remque(si);
		m = REASS_MBUF_STP(si);
		si = (struct stpiphdr *) si->si_next;
		if (so->so_state & SS_CANTRCVMORE || (m->m_len == 0))
			m_freem(m);
		else
			sbappend(&so->so_rcv,m);
	} while (si != (struct stpiphdr *) sp && si->si_seq == sp->rcv_nxt);
	sorwakeup(so);
	if (sp->ustat_pending) {
		if (seq_geq(sp->rcv_nxt, sp->ustat_list2))
			sp->ustat_pending = 0;
	}
	return (1); 
}

/* 
 * Note that this implementation does not make use of the implicit 
 * acknowledgments between acks to free data from send buffer.
 * Call current_time() once here for the whole list.
 * maxrtt is the (liberal) estimate for the link's current rtt
 */
int
stp_process_list(sp, dataptr, num_elements, maxrtt)
	struct stpcb *sp;
	u_long *dataptr;
	int num_elements;
	u_long maxrtt;
{
	u_long curtime = current_time();
	u_long list1, list2;
	int i_mod;
	while (num_elements > 1) {
		list1 = ntohl(*dataptr);
		dataptr++;
		list2 = ntohl(*dataptr);
		dataptr++;
		num_elements -= 2;
		if (seq_lt(list1, list2) && seq_geq(list1, sp->snd_una) &&
		    seq_leq(list2, sp->snd_nxt))
			if (stp_retransmit_list(sp, list1, list2, STAT, maxrtt, 
			    curtime) == STP_CONN_DROPPED)
				return STP_CONN_DROPPED;
		else {
			DBG(printf("bad elements l1 %x l2 %x una %x nxt %x\n", list1, list2, sp->snd_una, sp->snd_nxt););
		}
	}
	return 0;
}

int
stp_retransmit_list(sp, list1, list2, retransmit_type, maxrtt, curtime)
	struct stpcb *sp;
	u_long list1;
	u_long list2;
	int retransmit_type;
	u_long maxrtt;
	u_long curtime;
{
	u_long i;
	int i_mod;
	u_long temp;
	u_char retransmitted = 0;

	/* Always retransmit upon a USTAT, unless packet already retransmitted*/
	if (retransmit_type == USTAT) {
		for (i = list1; seq_lt(i,list2); i = SEQ_INC(i))  {
			i_mod = i % MAXBUFFER_STP;
			if (1 == sp->trans_buffer[i_mod].num_trans) {
				if (stp_retransmit(sp,i) == STP_CONN_DROPPED) 
					return STP_CONN_DROPPED;
				retransmitted = 1;
			}
		}
	} else {
		for (i = list1; seq_lt(i,list2); i = SEQ_INC(i)) {
			i_mod = i % MAXBUFFER_STP;
			temp = curtime - sp->trans_buffer[i_mod].timesent;
			if (temp > ( (maxrtt + (2 * (sp->s_rttvar >> 
			    SRTT_SHIFT)) ) << sp->snd_backoff) ) {
				if (stp_retransmit(sp,i) == STP_CONN_DROPPED) 
					return STP_CONN_DROPPED;
				retransmitted = 1;
			} 
		}
	}
	if (retransmitted) {
		/* Close the congestion window and reset send timer intervals */
		stp_closecwnd(sp);
		stp_set_output(sp);
	}
	return (0);
}

void            
stp_rcvurg(si, so, sp, m, seqno)
	struct stpiphdr *si;
	struct socket *so;  
	struct stpcb *sp;       
	struct mbuf *m;  
	u_long seqno;   
{
	if ((sp->s_state == STP_EST || 
	    sp->s_state == STP_HALF_CLOSE_PENDING ||
	    sp->s_state == STP_HALF_CLOSED) &&
	    (seq_subtract(seqno, sp->rcv_nxt) < sp->rcv_wnd)) {

		/* Fix so_oobmark */
		sp->rcv_up = seqno;
		if (seqno == sp->rcv_nxt) {
			so->so_oobmark = so->so_rcv.sb_cc;
		} else if (seq_gt(seqno, sp->rcv_nxt)) {
			so->so_oobmark = so->so_rcv.sb_cc + 
			    seq_subtract(seqno, sp->rcv_nxt) * 
			    sp->s_maxseg;
		}
		if (so->so_oobmark == 0)
			so->so_state |= SS_RCVATMARK;
		if (!sp->oob_pending) {
			/* 
			 * Must notify process of OOB data for 
			 * the first time.
			 */
			sohasoutofband(so);
			sp->s_oobflags &= ~(STPOOB_HAVEDATA | 
			    STPOOB_HADDATA);
		} 
		/* 
		 * If inline, pull character out of band 
		 * into s_iobc 
		 */
		if ((so->so_options & SO_OOBINLINE) == 0)
			stp_pulloutofband(so, si, m);
		/* 
		 * Even if the character has been stripped, we 
		 * still need to process this as an SD since 
	 	 * it has consumed a sequence number
		 */
		stp_rcvsd(si, so, sp, m, seqno);
		sp->oob_pending = FALSE;
	} else {
		m_freem(m);
	}
	return;
}
/*
 * We enter this routine if fast path processing fails, or if data was
 * tacked on to a connection control PDU; cases:
 * i) state not one in which data can be received 
 * ii) seqno == sp->rcv_nxt; check buffer/window; present data to user
 * iii) seqno >= sp->rcv_max; make sure buffer and window available
 * iv) rcv_nxt < seqno < rcv_max; insert
 * v) seqno > rcv_max; check buffer and window  
 */
u_char            
stp_rcvsd(si, so, sp, m, seqno)
	struct stpiphdr *si;
	struct socket *so;  
	struct stpcb *sp;       
	struct mbuf *m;  
	u_long seqno;   
{
	int seqno_mod = seqno % MAXBUFFER_STP;
	u_long i, i_mod;
	int ooblen = 0;
	u_char response_type = 0;
        
	/* check if arrival of packet is legal in this state */
	if (sp->s_state != STP_EST) {
		if (!(sp->s_state & (STP_HALF_CLOSE_PENDING |
		    STP_HALF_CLOSED | STP_FULL_CLOSE_PENDING | 
		    STP_FULL_CLOSED))) {
			m_freem(m);
			return response_type;
		}
	}
	if (seqno == sp->rcv_max) {
		/* Packet is next expected non-missing packet */
		if (seq_subtract(seqno, sp->rcv_nxt) < sp->rcv_wnd) {
			/* sufficient window to accept it */
			sp->rcv_max = SEQ_INC(sp->rcv_max);
			if (!stp_reass(sp, si, m)) {
				/* Do nothing */;
				DBG(printf("FAILED TO INSERT seqno %x sp->rcv_nxt %x\n", seqno, sp->rcv_nxt););
			}
			/* Check if USTAT pending */
			if (sp->ustat_pending)
				sp->ustat_pending++;
			if ((sp->ustat_pending == NUM_DUP_ACKS)) {
				if (stp_ustat_verify(sp))
					response_type |= STP_PDU_USTAT;
				sp->ustat_pending = 0;
			}
		} else {
			/* Overshot receiver buffer space */
			DBG(printf("Packet exceeds window: seqno %x rcv_nxt %x, rcv_wnd %d\n", seqno, sp->rcv_nxt, sp->rcv_wnd););
			m_freem(m);
			return response_type;
		}
	} else if (seqno == sp->rcv_nxt) {
		/* 
		 * Received the next in sequence; pass to
		 * reassembly buffer, which will update vars.
		 */
		if (!stp_reass(sp, si, m)) {
			/* Do nothing */;
			DBG(printf("FAILED TO INSERT seqno %x sp->rcv_nxt %x\n", seqno, sp->rcv_nxt););
		}
	} else if (seq_gt(seqno, sp->rcv_max)) {
		if (sp->ustat_pending && stp_ustat_verify(sp)) 
			response_type |= STP_PDU_USTAT;
		/* Next, mark a new ustat pending or send ustat*/
		sp->ustat_list1 = sp->rcv_max;
		sp->ustat_list2 = seqno;
		if (NUM_DUP_ACKS > 1) 
        		sp->ustat_pending = 1;
		else {
			response_type |= STP_PDU_USTAT;
			sp->ustat_pending = 0;
		}

		/* Clear out "received" flag for missing packets */
		for (i = sp->rcv_max; seq_lt(i, seqno); i = SEQ_INC(i)) {
			i_mod = i % MAXBUFFER_STP;
			sp->recv_buffer[i_mod].seqno = i;
			sp->recv_buffer[i_mod].received = 0;
		}
		/* Mark this one as received */
		if (!stp_reass(sp, si, m)) {
			/* Do nothing */;
			DBG(printf("FAILED TO INSERT seqno %x sp->rcv_nxt %x\n", seqno, sp->rcv_nxt););
		}
		sp->rcv_max = SEQ_INC(seqno);
	} else if (seq_gt(seqno, sp->rcv_nxt)) {
		/* 
		 * sp->rcv_nxt < seqno < sp->rcv_max 
		 * First, may need to update so->so_oobmark. 
		 */ 
		if (seq_lt(seqno, sp->rcv_up)) {
			for (i = sp->rcv_nxt; seq_lt(i, sp->rcv_up); 
			    i = SEQ_INC(i)) {
				i_mod = i % MAXBUFFER_STP;
				if (sp->recv_buffer[i_mod].received)
					ooblen += 
					    sp->recv_buffer[i_mod].num_bytes;
				else
					ooblen += sp->s_peermaxseg;
			}
			so->so_oobmark = so->so_rcv.sb_cc + ooblen;
		}
		if (!stp_reass(sp, si, m)) { /* New pkt for buffer*/
			/* Do nothing */;
			DBG(printf("FAILED TO INSERT (probably duplicate) seqno %x sp->rcv_nxt %x\n", seqno, sp->rcv_nxt););
		}
	} else {
		/* seqno < rcv_nxt */
		DBG(printf("Dropping it; seqno %x < rcv_nxt %x\n",seqno, sp->rcv_nxt););
		m_freem(m);
	}
	return response_type;
}   

/* If we've been waiting to send a USTAT, verify that it is still needed */
int
stp_ustat_verify(sp)
	struct stpcb *sp;
{
	int i, i_mod, gap_broken;

	gap_broken = 0;
	for (i = sp->ustat_list1; seq_lt(i, sp->ustat_list2); i = SEQ_INC(i)) {
		i_mod = i % MAXBUFFER_STP;
		if (1 == sp->recv_buffer[i_mod].received)
			gap_broken = 1;
	}
	return (!gap_broken);
}

u_char 
stp_rcvpoll(sp, seqno, timestamp)
	struct stpcb *sp;
	u_long seqno;
	u_long timestamp;
{
	struct socket *so;
	u_long i, i_mod, rcv_max_old;
	u_char response_type = 0;

	so = sp->s_inpcb->inp_socket;
	switch (sp->s_state) {
	case STP_HALF_CLOSE_PENDING:
	case STP_HALF_CLOSED:
	case STP_FULL_CLOSE_PENDING:
	case STP_FULL_CLOSED:
	case STP_EST:{
		if (sp->ustat_pending)
			sp->ustat_pending = 0;

		if(seqno != sp->rcv_max) {
			/* If seqno is within allowable receive window
		         * set rcv_max to it; otherwise, set to top of 
			 * window. 
			 */
			rcv_max_old = sp->rcv_max;
			if (seq_lt(seqno, 
			    seq_add(sp->rcv_nxt, sp->rcv_wnd))) 
				sp->rcv_max = seqno;
			else 
				sp->rcv_max = 
				    seq_add(sp->rcv_nxt, sp->rcv_wnd);
			/* 
			 * Clear out "received" flag for missing 
			 * packets
			 */
			for (i = rcv_max_old; seq_lt(i, sp->rcv_max); 
			    i = SEQ_INC(i)) {
				i_mod = i % MAXBUFFER_STP;
				sp->recv_buffer[i_mod].seqno = i;
				sp->recv_buffer[i_mod].received = 0;
			}
		}
		response_type |= STP_PDU_STAT;
		break;
	}
	case STP_BGN_SENT:
	case STP_BGN_DATA_SENT:
	case STP_BGN_DATA_END_SENT:
	case STP_LISTEN:
	case STP_CLOSE_RECV:
	case STP_LAST_ACK:
		break;
	default:
		/* Protocol error */
		stp_drop(sp, EINVAL);
		break;
	} /* switch */
	return response_type;
}

void 
stp_rcvstat(so, sp, m, seqno, window, timestamp)
	struct socket *so;
	struct stpcb *sp;
	struct mbuf *m;
	u_long seqno;
	u_long window;
	u_long timestamp;
{
	u_long *dataptr, elapsed, maxrtt;
	int acked, slen;
	struct stpiphdr *si;
	int num_elements = 0;
	int progress = 0;

	sp->no_stat_response = 0;
	sp->tinygram_out = FALSE;  
	/* Note:  The use of slen helps in protecting against buggy 3com cards*/
	si = mtod(m, struct stpiphdr *);
	slen = ((struct ip *) si)->ip_len;
	switch (sp->s_state) {
	case STP_EST:
	case STP_CLOSE_RECV:

		/* Get pointer to payload of STAT (actually 1st list element)*/
		dataptr = (u_long *) (mtod(m,caddr_t) + 
		    sizeof(struct stpiphdr));
		elapsed = time_elapsed(timestamp);
		/* Make sure we're not responding to an old STAT */
		if (sp->last_poll_tstamp >= timestamp)  
			break;
		/* 
		 * maxrtt holds the maximum of s_srtt and elapsed 
		 * The use of maxrtt helps to prevent spurious 
		 * retransmissions if the RTT of the connection 
		 * suddenly jumps.
		 */
		maxrtt = (elapsed < (sp->s_srtt >> SRTT_SHIFT) ? 
		    (u_long) (sp->s_srtt >> SRTT_SHIFT) : elapsed);
		stp_updatertt(sp, time_elapsed(timestamp));
		sp->last_poll_tstamp = timestamp;
		if (window != sp->snd_wnd) {
			sp->snd_wnd = window;
			progress = 1;
		}
		if (sp->snd_wnd && (sp->s_timer & STP_TIMER_PERSIST)) 
			/* 
			 * credit to send more data now. set_output() 
			 * called below if data is pending, which will 
			 * turn on SEND timer and POLL.
			 */
			stp_cancel_timer(sp, STP_TIMER_PERSIST);

		if (seq_gt(seqno, sp->snd_una)) { /* New data acked */
			progress = 1;
			acked = acked_byte_count(sp, seqno);
			if (acked < 0 || acked > (sp->s_maxseg * 
			    seq_subtract(seqno, sp->snd_una))) {
				/* Do nothing */;
				DBG(printf("acked problem %d seqno %x snd_una %x\n", acked, seqno, sp->snd_una););
			}
			sbdrop(&so->so_snd, acked);
			sp->snd_off -= acked;
			if (sp->snd_up > 0) {
				sp->snd_up -= acked;
				if (sp->snd_up < 0)
					sp->snd_up = 0;
			}
			stp_opencwnd(sp, seq_subtract(seqno, 
			    sp->snd_una));
			if (so->so_snd.sb_flags & SB_NOTIFY)
				sowwakeup(so);
		}
		sp->snd_una = seqno;

		/* Find out how many list elements */
		num_elements = (slen - sizeof(struct stphdr)) / 4;
		if (num_elements == 1) {
			DBG(printf("Dropping due to num_elements =1\n"););
			sp = stp_drop(sp, EINVAL);
			break;
		}
		/* Must check value of sp in case the connection */
		/* was dropped due to MAX_RETRANS somewhere in loop*/
		if (sp == 0)
			return;
		/* Sanity check the number of elements (not done yet) */
		if (num_elements) {
			if (stp_process_list(sp, dataptr, num_elements, maxrtt) == STP_CONN_DROPPED) 
				break;
		}

		/* check if we want to reduce POLLing frequency */
		if ((sp->snd_nxt == sp->snd_una) && 
		    (so->so_snd.sb_cc == 0)) {
			sp->poll_pending = 0;
			sp->send_pending = 0;
			stp_cancel_timer(sp, STP_TIMER_SEND);
			if (sp->disc_pending) 
				stp_disconnect(sp);
			if (sp->shutdown_pending)
				stp_shutdown(sp);
		} else {
			stp_set_output(sp);
		}	
		if (!progress) {
			if (sp->no_progress++ > STP_NO_PROGRESS) {
				stp_drop(sp, EPIPE);
				DBG(printf("Dropping--lack of progress\n"););
				break;
			}
		} else
			sp->no_progress = 0;
		break;

	case STP_HALF_CLOSE_PENDING:
	case STP_HALF_CLOSED:
	case STP_FULL_CLOSE_PENDING:
	case STP_FULL_CLOSED:
	case STP_LAST_ACK:
		/* 
		 * Although we have sent END, old POLLs may still be 
		 * being answered, so ignore them. 
		 */
		break;

	case STP_BGN_SENT:
	case STP_BGN_DATA_SENT:
		/* 
		 * If BGAK has been lost and more than one packet has 
		 * been sent, this may be a legal reception.  Ignore 
		 * it (wait for BGNAK).
		 */
		break;

	case STP_BGN_DATA_END_SENT:
	case STP_LISTEN:
	default:
		/* No POLLs should be outstanding in these states */
		stp_drop(sp, EINVAL);
		break;
	} /* switch */
}

void 
stp_rcvustat(so, sp, dataptr, seqno)
	struct socket *so;
	struct stpcb *sp;
	u_long *dataptr;
	u_long seqno;
{
	u_long list1, list2;
	int acked;

	switch (sp->s_state) {
	case STP_HALF_CLOSE_PENDING:
	case STP_HALF_CLOSED:
	case STP_FULL_CLOSE_PENDING:
	case STP_FULL_CLOSED:
	case STP_EST:
		list1 = ntohl((u_long) *dataptr);
		dataptr++;
		list2 = ntohl((u_long) *dataptr);
		if (stp_retransmit_list(sp, list1, list2, USTAT, 0, 0) ==
		    STP_CONN_DROPPED)
			break;     
		if (seq_gt(seqno, sp->snd_una)) { /* New data acked */
			acked = acked_byte_count(sp, seqno);		
			sbdrop(&so->so_snd, acked);
			sp->snd_off -= acked;
			/* opencwnd here?? */
			stp_opencwnd(sp, seqno - sp->snd_una);
		}
		sp->snd_una = seqno;
		break;

	case STP_LAST_ACK:
	case STP_CLOSE_RECV:
		/* 
		 * Although peer sender has closed, USTAT may have 
		 * been misordered by the network
		 */
		break;

	case STP_BGN_SENT:
	case STP_BGN_DATA_SENT:
		/* 
		 * If BGAK has been lost and more than one packet has 
		 * been sent, this may be a legal reception.  Ignore 
		 * it (wait for BGNAK).
		 */
		break;
	
	case STP_BGN_DATA_END_SENT:
	case STP_LISTEN:
	default:
		/* This packet is an illegal reception */
		stp_drop(sp, EINVAL);
		break;
	} /* switch */
}

u_char 
stp_rcvbgn(so, sp, inp, m, seqno, window, timestamp)
	struct socket *so;
	struct stpcb *sp;
	struct inpcb *inp;
	struct mbuf *m;
	u_long seqno;
	u_long window;
	u_long timestamp;
{
	struct stpiphdr *si;
	struct in_addr laddr;
	u_char return_value = 0;

	/* Get pointers to stpiphdr and data */
	si = mtod(m, struct stpiphdr *);

	switch (sp->s_state) {
 	case STP_LISTEN:{
		struct mbuf *am;
		struct sockaddr_in *sin;


		/*
		 * get mbuf for client's address and port.  set local
		 * address in pcb.  connect pcb to peer.  Allocate and
		 * initialize IP and TCP header in template.  initialize
		 * pcb.  Ack the BGN and change state.
		 */
		am = m_get(M_DONTWAIT, MT_SONAME);
		am->m_len = sizeof(struct sockaddr_in);
		sin = mtod(am, struct sockaddr_in *);
		sin->sin_family = AF_INET;
		sin->sin_len = sizeof(*sin);
		sin->sin_addr = si->si_src;
		sin->sin_port = si->si_sport;
		bzero((caddr_t) sin->sin_zero, sizeof(sin->sin_zero));
		
		laddr = inp->inp_laddr;
		if (inp->inp_laddr.s_addr == INADDR_ANY)
			inp->inp_laddr = si->si_dst;
		if (in_pcbconnect(inp, am)) {
			inp->inp_laddr = laddr;
			(void) m_free(am);
			sp->s_state = STP_CLOSED;
			DBG(printf("Connection dropped\n"););
			return 0;
		}
		(void) m_free(am);
			
		sp->s_template = stp_template(sp);
		if (sp->s_template == 0) {
			sp = stp_drop(sp, ENOBUFS);
			DBG(printf("Connection dropped\n"););
			return 0;
		}
		/* connect socket */
		soisconnected(so);
		stp_start_timer(sp, STP_TIMER_KEEPALIVE, NULL);
		
		/* initialize sp */
		sp->s_instance = ntohs(si->si_inc);
		sp->rcv_max = sp->rcv_irs = sp->rcv_nxt = seqno;
		sp->snd_wnd = window;

		sp->s_state = STP_EST;
		sp->bgnak_timestamp = timestamp;
		return_value |= STP_PDU_BGNAK;
		
		break;
	}
 	case STP_EST:{
		/* Peer must have lost our BGNAK */
		sp->snd_wnd = window;
		sp->bgnak_timestamp = timestamp;
		return_value |= STP_PDU_BGNAK;
		break;
	}
	case STP_BGN_SENT:
	case STP_BGN_DATA_SENT:
	case STP_BGN_DATA_END_SENT:
		/* 
		 * Simultaneous open; how can this happen?
		 */
		DBG(printf("Error:  simultaneous open\n"););
		break;
	default:
		/* closing states; do nothing */
		break;
	} /* switch */
	return return_value;
}


void 
stp_rcvbgnak(so, sp, seqno, window, timestamp)
	struct socket *so;
	struct stpcb *sp;
	u_long seqno;
	u_long window;
	u_long timestamp;
{
	struct stpiphdr *si;
	int acked;

	switch (sp->s_state) {
	case STP_BGN_SENT:
	case STP_BGN_DATA_SENT:
		/* Initialize receive variables */
		sp->rcv_max = sp->rcv_irs = sp->rcv_nxt = seqno;
		sp->snd_wnd = window;
		sp->s_state = STP_EST;
		/* connect socket */
		soisconnected(so);
		stp_cancel_timer(sp, STP_TIMER_BGN);
		sp->cc_count = 0;

		/* Note: this first RTT estimate may be long */  
		stp_updatertt(sp, time_elapsed(timestamp));

		if (sp->s_state == STP_BGN_DATA_SENT) {
			/* Implicit ACK of first packet */
			acked = acked_byte_count(sp, seqno);
			sbdrop(&so->so_snd, acked);
			sp->snd_off -= acked;
			stp_opencwnd(sp, 1);
			sp->snd_una = SEQ_INC(sp->snd_una);
		}

		stp_start_timer(sp, STP_TIMER_KEEPALIVE, NULL);
		stp_set_output(sp);
		/* In case we have meanwhile received disconnect or shutdown */
		if (sp->disc_pending)
			stp_disconnect(sp);
		if (sp->shutdown_pending)
			stp_shutdown(sp);
		break;

	case STP_BGN_DATA_END_SENT:
	case STP_LISTEN:
		DBG(printf("protocol error, bgnak recv'd in %x state\n", sp->s_state););
		break;
	default:
		/* states LISTEN, EST, closing states */
		break;
	} /* switch */
}

u_char 
stp_rcvend(so, sp, seqno)
	struct socket *so;
	struct stpcb *sp;
	u_long seqno;
{
	u_char response_type = 0;
	/* Drop if haven't yet received all of the data */
	if (seqno != sp->rcv_nxt)
		return 0;
	switch (sp->s_state) {
	case STP_EST:
		/* Mark socket as write-only */ 
		socantrcvmore(so);
		sp->s_state = STP_CLOSE_RECV;
		response_type |= STP_PDU_ENDAK;
		break;

	case STP_HALF_CLOSE_PENDING:
		socantrcvmore(so);
		/* FALL THROUGH */

	case STP_FULL_CLOSE_PENDING:
		/* Have sent END, now receive END (end collision) */
		sp->s_state = STP_LAST_ACK;
		response_type |= STP_PDU_ENDAK;
		break;

	case STP_FULL_CLOSED:
	case STP_HALF_CLOSED:
		/* Connection is now completely closed-- send ENDAK directly */	
		stp_sndcc_close(sp, STP_PDU_ENDAK);
		sp = stp_close(sp);
		/* This will force calling function to return */
		response_type = STP_PDU_RST;
		break;

	case STP_CLOSE_RECV:
	case STP_LAST_ACK:
		/* ENDAK was lost?? */
		response_type |= STP_PDU_ENDAK;
		break;

	case STP_LISTEN:
		/* BGN lost? Just ignore it */
		break;

	case STP_BGN_SENT:
	case STP_BGN_DATA_SENT:
	case STP_BGN_DATA_END_SENT:
		/* this is a protocol error */
		sp = stp_drop(sp, EINVAL);
		break;
	default:
		DBG(printf("End recieved, not in EST state %d\n", sp->s_state););
		break;
	} /* switch */
	return response_type;
}

void 
stp_rcvendak(so, sp, seqno)
	struct socket *so;
	struct stpcb *sp;
	u_long seqno;
{
	int acked;
	switch (sp->s_state) {
	case STP_LAST_ACK:
		sp = stp_close(sp);
		break;
	case STP_FULL_CLOSE_PENDING:
		soisdisconnected(so);
		stp_cancel_timer(sp, STP_TIMER_END);
		sp->cc_count = 0;
		sp->s_state = STP_FULL_CLOSED;
		stp_start_timer(sp, STP_TIMER_KEEPALIVE, NULL);
		break;

	case STP_HALF_CLOSE_PENDING:
		stp_cancel_timer(sp, STP_TIMER_END);
		sp->cc_count = 0;
		sp->s_state = STP_HALF_CLOSED;
		stp_start_timer(sp, STP_TIMER_KEEPALIVE, NULL);
		break;

	case STP_BGN_DATA_END_SENT:
		/* 
		 * ACK data.  We've sent one segment numbered 
		 * "sp->snd_una" 
		 */
		acked = acked_byte_count(sp, SEQ_INC(sp->snd_una));
		sbdrop(&so->so_snd, acked);
		sp->snd_off -= acked;
		stp_cancel_timer(sp, STP_TIMER_BGN);
		stp_start_timer(sp, STP_TIMER_KEEPALIVE, NULL);
		sp->s_state = STP_HALF_CLOSED;
		break;

	case STP_FULL_CLOSED:
	case STP_HALF_CLOSED:
		/* Might be retransmission of ENDAK */
		break;
	case STP_EST:
	case STP_CLOSE_RECV:
		if (sp->shutdown_pending || sp->disc_pending) {
			/* Could perform additional sanity checks here*/
			/* Stop data transfer timers */
			sp->poll_pending = 0;
			sp->send_pending = 0;
			stp_cancel_timer(sp, STP_TIMER_SEND);
			if (sp->disc_pending) {
				/* Check this */
				soisdisconnected(so);
			}
			if (sp->s_state == STP_EST) {
				stp_start_timer(sp, STP_TIMER_KEEPALIVE, NULL);
				sp->s_state = STP_FULL_CLOSED;
			} else { 
				/* Peer has closed */
				sp = stp_close(sp);
			}
			break;	
		}
		/* FALL THROUGH */

	case STP_BGN_SENT:
	case STP_BGN_DATA_SENT:
	case STP_LISTEN:
		/* Haven't sent END */
		DBG(printf("Received ENDAK but haven't sent END\n"););
		break;
	default:
		break;
	} /* switch */
}

/* Mimics TCP  */
void 
stp_rcvrst(so, sp, m)
	struct socket *so;
	struct stpcb *sp;
	struct mbuf *m;
{
	switch (sp->s_state) {
	case STP_BGN_SENT:
	case STP_BGN_DATA_SENT:
	case STP_BGN_DATA_END_SENT:
		so->so_error = ECONNREFUSED;
		goto close;
	case STP_EST:
	case STP_HALF_CLOSE_PENDING:
	case STP_HALF_CLOSED:
	case STP_FULL_CLOSE_PENDING:
	case STP_FULL_CLOSED:
		so->so_error = ECONNRESET;
	  close:
		sp->s_state = STP_CLOSED;
		sp = stp_close(sp);
		break;
	case STP_LAST_ACK:
	case STP_CLOSE_RECV:
		sp = stp_close(sp);
	}
}

#endif
